/**
 * MasicPlayer
 * <p/>
 * User: joakimd
 * Date: 2013-03-11
 * Time: 12:15
 */
package org.dru.masic {
import flash.events.Event;
import flash.events.SampleDataEvent;
import flash.media.Sound;

import org.dru.masic.event.MasicContainerEvent;
import org.dru.masic.utils.Logger;

public final class MasicPlayer {
    private var _sequencer: MasicSequencer;
    private var _components: Vector.<MasicComponent>;
    private var _valid: Boolean;
    private var _sound: Sound;

    public function MasicPlayer() {
        _sequencer = null;
        _components = new Vector.<MasicComponent>();
        _valid = false;
        _sound = null;
    }

    public function get sequencer(): MasicSequencer {
        return _sequencer;
    }

    public function set sequencer(value: MasicSequencer): void {
        if (value != _sequencer) {
            if (_sequencer != null) {
                uninstallSequencer(_sequencer);
            }
            _sequencer = value;
            if (_sequencer != null) {
                installSequencer(_sequencer);
            }
            invalidate();
            updateSound();
        }
    }

    public function get container(): MasicContainer {
        return (sequencer != null ? sequencer.container : null);
    }

    internal function get components(): Vector.<MasicComponent> {
        return _components;
    }

    private function installSequencer(sequencer: MasicSequencer): void {
        installContainer(sequencer.container);
    }

    private function installContainer(container: MasicContainer): void {
        Logger.log(Logger.DEBUG, "installContainer");
        container.addEventListener(MasicContainerEvent.COMPONENT_ADDED, onComponentAdded);
        container.addEventListener(MasicContainerEvent.COMPONENT_REMOVED, onComponentRemoved);
        for each (var component: MasicComponent in container.components) {
            installComponent(component);
        }
    }

    private function installComponent(component: MasicComponent): void {
        for each (var output: MasicOutput in component.outputs) {
            output.addEventListener(Event.CHANGE, onPortChange);
        }
    }

    private function uninstallSequencer(sequencer: MasicSequencer): void {
        uninstallContainer(sequencer.container);
    }

    private function uninstallContainer(container: MasicContainer): void {
        container.removeEventListener(MasicContainerEvent.COMPONENT_ADDED, onComponentAdded);
        container.removeEventListener(MasicContainerEvent.COMPONENT_REMOVED, onComponentRemoved);
        for each (var component: MasicComponent in container.components) {
            uninstallComponent(component);
        }
    }

    private function uninstallComponent(component: MasicComponent): void {
        for each (var output: MasicOutput in component.outputs) {
            output.removeEventListener(Event.CHANGE, onPortChange);
        }
    }

    private function get valid(): Boolean {
        return _valid;
    }

    private function set valid(value: Boolean): void {
        _valid = value;
    }

    private function invalidate(): void {
        valid = false;
    }

    private function validate(): void {
        if (!valid) {
            components.splice(0, components.length);
            for each (var component: MasicComponent in container.components) {
                components.push(component);
            }
            components.push(container.speaker);
            components.sort(MasicUtils.compareComponentLength);
            valid = true;
        }
    }

    private function updateSound(): void {
        if (sequencer != null) {
            playSound();
        }
        else {
            closeSound();
        }
    }

    private function playSound(): void {
        if (_sound == null) {
            _sound = new Sound();
            _sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
            _sound.play();
        }
    }

    private function closeSound(): void {
        if (_sound != null) {
            _sound.removeEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
            _sound.close();
            _sound = null;
        }
    }

    private function processTick(samplesToTick: int): void {
        for each (var component: MasicComponent in components) {
            component.processTick(samplesToTick);
        }
    }

    private function processSamples(start: int, end: int): void {
        for each (var component: MasicComponent in components) {
            for each (var input: MasicInput in component.inputs) {
                MasicUtils.processSamples(input, start, end);
            }
            component.processSamples(start, end);
        }
    }

    private function onComponentAdded(event: MasicContainerEvent): void {
        installComponent(event.component);
    }

    private function onComponentRemoved(event: MasicContainerEvent): void {
        uninstallComponent(event.component);
    }

    private function onPortChange(event: Event): void {
        invalidate();
    }

    private function onSampleData(event: SampleDataEvent): void {
        validate();
        var start: int = 0;
        while (start < MasicConstants.SAMPLE_BUFFER_SIZE) {
            const amount: int = Math.min(MasicConstants.SAMPLE_BUFFER_SIZE - start, sequencer.currentSamplesToTick);
            if (amount == 0) {
                sequencer.nextSamplesToTick();
                processTick(sequencer.currentSamplesToTick);
                sequencer.processTick();
            }
            else {
                sequencer.countDownSamplesToTick(amount);
                processSamples(start, start + amount);
                start += amount;
            }
        }
        container.speaker.writeSampleData(event.data, 0, MasicConstants.SAMPLE_BUFFER_SIZE);
    }
}
}
